package Detour

import (
	"encoding/binary"
	"math"
)

type DTPolyRef uint32
type DTStatus uint32
type DTTileRef uint32

const (
	DT_VERTS_PER_POLYGON = 6
	DT_NAVMESH_MAGIC     = 'D'<<24 | 'N'<<16 | 'A'<<8 | 'V'
	DT_NAVMESH_VERSION   = 7
	DT_NULL_LINK         = uint(0xffffffff)
	DT_EXT_LINK          = 0x8000

	//dtPolyTypes
	DT_POLYTYPE_GROUND             = 0
	DT_POLYTYPE_OFFMESH_CONNECTION = 1

	DT_OFFMESH_CON_BIDIR = 1
	DT_MAX_AREAS         = 64

	DT_TILE_FREE_DATA = 0x01

	DT_STRAIGHTPATH_START              = 0x01 //< The vertex is the start position in the path.
	DT_STRAIGHTPATH_END                = 0x02 //< The vertex is the end position in the path.
	DT_STRAIGHTPATH_OFFMESH_CONNECTION = 0x04 //< The vertex is the start of an off-mesh connection.
)

////////////////dtNavMeshParams///////////////////////
type DTNavMeshParams struct {
	Orig       []float64
	TileWidth  float64
	TileHeight float64
	MaxTiles   int
	MaxPolys   int
}

func NewDTNavMeshParams() *DTNavMeshParams {
	dtNavParam := &DTNavMeshParams{}
	dtNavParam.Orig = make([]float64, 3)
	return dtNavParam
}
func (this *DTNavMeshParams) Clone() *DTNavMeshParams {
	copyParam := NewDTNavMeshParams()

	for i := 0; i < len(this.Orig); i++ {
		copyParam.Orig[i] = this.Orig[i]
	}
	copyParam.TileWidth = this.TileWidth
	copyParam.TileHeight = this.TileHeight
	copyParam.MaxTiles = this.MaxTiles
	copyParam.MaxPolys = this.MaxPolys
	return copyParam
}

///////////////////////dtLink///////////////////////
type DTLink struct {
	polyRef DTPolyRef
	Next    uint
	Edge    byte
	Side    byte
	Bmin    byte
	Bmax    byte
}

func NewDTLink() *DTLink {
	dtLink := new(DTLink)
	return dtLink
}

func (this *DTLink) FromBytes(array []byte, start int) int {
	this.polyRef = DTPolyRef(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.Next = uint(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.Edge = array[start : start+1][0]
	start++
	this.Side = array[start : start+1][0]
	start++
	this.Bmin = array[start : start+1][0]
	start++
	this.Bmax = array[start : start+1][0]
	start++
	return start
}

///////////////////////dtPoly///////////////////////
type DTPoly struct {
	FirstLink uint
	Verts     []uint16
	Neis      []uint16

	Flags       uint16
	VertCount   byte
	AreaAndtype byte
}

func NewDTPoly() *DTPoly {
	dtpoly := new(DTPoly)
	return dtpoly
}

func (this *DTPoly) FromBytes(array []byte, start int) int {
	this.FirstLink = uint(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.Verts = make([]uint16, DT_VERTS_PER_POLYGON)
	for i := 0; i < DT_VERTS_PER_POLYGON; i++ {
		this.Verts[i] = binary.LittleEndian.Uint16(array[start:])
		start += 2
	}
	this.Neis = make([]uint16, DT_VERTS_PER_POLYGON)
	for i := 0; i < DT_VERTS_PER_POLYGON; i++ {
		this.Neis[i] = binary.LittleEndian.Uint16(array[start:])
		start += 2
	}
	this.Flags = binary.LittleEndian.Uint16(array[start:])
	start += 2
	this.VertCount = array[start : start+1][0]
	start++
	this.AreaAndtype = array[start : start+1][0]
	start++
	return start
}

func (this *DTPoly) SetArea(a byte) {
	this.AreaAndtype = byte(int(this.AreaAndtype)&0xc0 | (int(a) & 0x3f))
}

func (this *DTPoly) SetType(t byte) {
	this.AreaAndtype = byte(int(this.AreaAndtype)&0x3f | (int(t) << 6))
}

func (this *DTPoly) getArea() byte {
	return byte(int(this.AreaAndtype) & 0x3f)
}
func (this *DTPoly) getType() byte {
	return byte(int(this.AreaAndtype) >> 6)
}

///////////////////////DTMeshHeader///////////////////////
type DTMeshHeader struct {
	Magic           int
	Version         int
	X               int
	Y               int
	Layer           int
	UserId          uint
	PolyCount       int
	VertCount       int
	MaxLinkCount    int
	DetailMeshCount int
	DetailVertCount int
	DetailTriCount  int
	BvNodeCount     int

	OffMeshConCount int
	OffMeshBase     int
	WalkableHeight  float64
	WalkableRadius  float64
	WalkableClimb   float64
	Bmin            []float64
	Bmax            []float64

	BvQuantFactor float64
}

func NewDTMeshHeader() *DTMeshHeader {
	meshHeader := &DTMeshHeader{}
	meshHeader.Bmin = make([]float64, 3)
	meshHeader.Bmax = make([]float64, 3)
	return meshHeader
}

func (this *DTMeshHeader) FromBytes(array []byte, start int) int {
	this.Magic = int(binary.LittleEndian.Uint32(array))
	start += 4
	this.Version = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.X = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.Y = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.Layer = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.UserId = uint(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.PolyCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.VertCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.MaxLinkCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.DetailMeshCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.DetailVertCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.DetailTriCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.BvNodeCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.OffMeshConCount = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.OffMeshBase = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4

	this.WalkableHeight = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
	start += 4
	this.WalkableRadius = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
	start += 4
	this.WalkableClimb = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
	start += 4
	this.Bmin = make([]float64, 3)
	for i := 0; i < 3; i++ {
		this.Bmin[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
		start += 4
	}
	this.Bmax = make([]float64, 3)
	for i := 0; i < 3; i++ {
		this.Bmax[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
		start += 4
	}
	this.BvQuantFactor = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
	start += 4
	return start
}

///////////////////////dtPolyDetail///////////////////////
type DTPolyDetail struct {
	VertBase  uint
	TriBase   uint
	VertCount byte
	TriCount  byte
}

func (this *DTPolyDetail) FromBytes(array []byte, start int) int {
	this.VertBase = uint(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.TriBase = uint(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	this.VertCount = array[start]
	start++
	this.TriCount = array[start]
	start++
	return start
}

///////////////////////dtBVNode///////////////////////
type DTBVNode struct {
	Bmin []uint16 //3
	Bmax []uint16 //3
	I    int
}

func (this *DTBVNode) FromBytes(array []byte, start int) int {
	this.Bmin = make([]uint16, 3)
	this.Bmax = make([]uint16, 3)
	for j := 0; j < 3; j++ {
		this.Bmin[j] = binary.LittleEndian.Uint16(array[start:])
		start += 2
	}

	for j := 0; j < 3; j++ {
		this.Bmax[j] = binary.LittleEndian.Uint16(array[start:])
		start += 2
	}
	this.I = int(int32(binary.LittleEndian.Uint32(array[start:])))
	start += 4
	return start
}

///////////////////////dtOffMeshConnection/////////////
type DTOffMeshConnection struct {
	Pos    []float64 //6
	Rad    float64
	Poly   uint16
	Flags  byte
	Side   byte
	UserId uint
}

func (this *DTOffMeshConnection) FromBytes(array []byte, start int) int {
	this.Pos = make([]float64, 6)
	for i := 0; i < 6; i++ {
		this.Pos[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
		start += 4
	}
	this.Rad = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
	start += 4
	this.Poly = binary.LittleEndian.Uint16(array[start:])
	start += 2
	this.Flags = array[start]
	start++
	this.Side = array[start]
	start++
	this.UserId = uint(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	return start
}

///////////////////////dtRawTileData/////////////
type DTRawTileData struct {
	Header *DTMeshHeader
	Polys  []DTPoly
	Verts  []float64
	Links  []DTLink

	DetailMeshes []DTPolyDetail
	DetailVerts  []float64
	DetailTris   []byte
	BVTree       []DTBVNode
	OffMeshCons  []DTOffMeshConnection

	Flags int
}

func (this *DTRawTileData) FromBytes(array []byte, start int) int {
	this.Header = NewDTMeshHeader()
	start = this.Header.FromBytes(array, start)
	//Header
	count := this.Header.PolyCount
	this.Polys = make([]DTPoly, count)
	for i := 0; i < count; i++ {
		start = this.Polys[i].FromBytes(array, start)
	}
	//Polys
	count = this.Header.VertCount * 3
	this.Verts = make([]float64, count)
	for i := 0; i < count; i++ {
		this.Verts[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
		start += 4
	}
	//Verts
	count = this.Header.MaxLinkCount
	this.Links = make([]DTLink, count)
	for i := 0; i < count; i++ {
		start = this.Links[i].FromBytes(array, start)
	}
	//Links
	count = this.Header.DetailMeshCount
	this.DetailMeshes = make([]DTPolyDetail, count)
	for i := 0; i < count; i++ {
		start = this.DetailMeshes[i].FromBytes(array, start)
	}
	//DetailMeshes
	count = this.Header.DetailVertCount * 3
	this.DetailVerts = make([]float64, count)
	for i := 0; i < count; i++ {
		this.DetailVerts[i] = float64(math.Float32frombits(binary.LittleEndian.Uint32(array[start:])))
		start += 4
	}
	//DetailVerts
	count = this.Header.DetailTriCount * 4
	this.DetailTris = make([]byte, count)
	for i := 0; i < count; i++ {
		this.DetailTris[i] = array[start+i]
	}
	//DetailTris
	start += count
	count = this.Header.BvNodeCount
	this.BVTree = make([]DTBVNode, count)
	for i := 0; i < count; i++ {
		start = this.BVTree[i].FromBytes(array, start)
	}
	//BVTree
	count = this.Header.OffMeshConCount
	this.OffMeshCons = make([]DTOffMeshConnection, count)
	for i := 0; i < count; i++ {
		start = this.OffMeshCons[i].FromBytes(array, start)
	}
	//OffMeshCons
	this.Flags = int(binary.LittleEndian.Uint32(array[start:]))
	start += 4
	return start
}

///////////////////////dtMeshTile/////////////
type DTMeshTile struct {
	Salt          uint
	LinksFreeList uint
	Header        *DTMeshHeader
	Polys         []DTPoly
	Verts         []float64
	Links         []DTLink
	DetailMeshes  []DTPolyDetail
	DetailVerts   []float64
	DetailTris    []byte

	BVTree      []DTBVNode
	OffMeshCons []DTOffMeshConnection

	Data     *DTRawTileData
	DataSize int
	Flags    int
	Next     *DTMeshTile
}

func NewDTMeshTile() *DTMeshTile {
	return &DTMeshTile{}
}
func NewDTMeshTileNum(num int) []*DTMeshTile {
	meshTiles := make([]*DTMeshTile, num)
	for i := 0; i < num; i++ {
		meshTiles[i] = NewDTMeshTile()
	}
	return meshTiles
}
